﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System;
using System.Runtime.Serialization;
using System.Threading;
using System.Linq;
using System.Collections.Generic;
using System.Configuration;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using BrainTechLLC.ThreadSafeObjects;
using BrainTechLLC;
using System.Diagnostics;

namespace BrainTechLLC
{
    /// <summary>
    /// Central point for loading, resolving type names, and creating objects of various types
    /// </summary>
#if NO_SILVERLIGHT
    [Browsable(true)]
    [TypeConverter(typeof(ExpandableObjectConverter))]
#endif
    public partial class TypeProvider : Lockable, ITypeLookup
    {
        public static ThreadSafeLookup<string, Type> _typesForSerialize = new ThreadSafeLookup<string, Type>();

        public static void AddTypesForSerialize(IEnumerable<Type> types)
        {
            foreach (var type in types)
            {
                if (type.IsTypeOkay())
                    _typesForSerialize.AddOrSet(type.FullName, type);
            }
        }

        public static object _lockObj = new object();

        private static TypeProvider _instance;

        public static TypeProvider GetInstance()
        {
            if (_instance == null)
            {
                lock (_lockObj)
                {
                    if (_instance == null)
                    {
                        Interlocked.CompareExchange<TypeProvider>(ref _instance, new TypeProvider(), null);
                    }
                }
            }

            return _instance;
        }

        private static string PluginDLLWildcard = "BrainTechLLC*.dll";
        private static string PluginPrefix = ""; //"BrainTechLLC";
        private static readonly object[] emptyObjects = new object[0];

        public ThreadSafeLookupOnDemandWithDelegate<string, Type> _typeLookup = new ThreadSafeLookupOnDemandWithDelegate<string, Type>(StringComparer.OrdinalIgnoreCase);

        public ThreadSafeList<DLLContents> _dllContentList = new ThreadSafeList<DLLContents>();

        public ThreadSafeLookupOnDemandWithDelegate<Type, List<Type>> _cachedTypesSupportingInterface = new ThreadSafeLookupOnDemandWithDelegate<Type, List<Type>>();

        public ThreadSafeLookupOnDemandWithDelegate<Type, ConstructorInfo> _constructorLookup = new ThreadSafeLookupOnDemandWithDelegate<Type, ConstructorInfo>();

        public ThreadSafeList<DLLContents> DLLContentList { get { return _dllContentList; } }

        public DLLContents GetDLLContents(string dllName)
        {
            DLLContents contents = FindDLLContents(dllName);
            if (contents != null) return contents;

            DLLContents newContents = new DLLContents() { DLLName = dllName };
            _dllContentList.Add(newContents);
            return newContents;
        }

        public List<ITypeDescription> AllTypesThatImplement(string baseClass)
        {
            return AllTypesThatImplement(GetType(baseClass));
        }

        public List<ITypeDescription> AllTypesThatImplement(Type t)
        {
            List<ITypeDescription> results = new List<ITypeDescription>();

            _dllContentList.AquireLock();
            {
                foreach (var content in _dllContentList._list)
                {
                    foreach (var v in content.TypesInDLL.Lookup)
                    {
                        if (v.Value.ClassType != t && t.IsAssignableFrom(v.Value.ClassType))
                            results.Add(v.Value);
                    }
                }
            }
            _dllContentList.ReleaseLock();

            return new List<ITypeDescription>(results);
        }

        public List<MethodInfo> FindMethods(Type t, string methodName, Type returnType, params Type[] paramTypes)
        {
            List<MethodInfo> results = new List<MethodInfo>();

            _dllContentList.AquireLock();
            {
                try
                {
                    for (int n = 0; n < _dllContentList._list.Count; n++)
                    {
                        var content = _dllContentList._list[n];
                        var types = content.TypesInDLL.AllItems;
                        for (int i = 0; i < types.Count; i++)
                        {
                            var type = types[i].ClassType;

                            if (t != null && t != type)
                                continue;

                            // BindingFlags.Instance | 
                            MethodInfo[] methods = type.GetMethods(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase);
                            foreach (MethodInfo m in methods)
                            {
                                if (methodName != null && m.Name.IndexOf(methodName, StringComparison.OrdinalIgnoreCase) < 0)
                                    continue;

                                if (returnType != null && m.ReturnType != returnType)
                                    continue;

                                bool match = true;

                                if (paramTypes != null && paramTypes.Length > 0)
                                {
                                    ParameterInfo[] pi = m.GetParameters();
                                    if (pi.Length != paramTypes.Length)
                                    {
                                        match = false;
                                    }
                                    else
                                    {
                                        for (int a = 0; a < paramTypes.Length; a++)
                                        {
                                            if (pi[a].ParameterType != paramTypes[a])
                                            {
                                                match = false;
                                                break;
                                            }
                                        }
                                    }
                                }

                                if (!match)
                                    continue;

                                results.Add(m);
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                    Debug.WriteLine(ex.ToString());
                }
                finally
                {
                    _dllContentList.ReleaseLock();
                }
            }

            return results;
        }

        public DLLContents FindDLLContents(string dllName)
        {
            string match = dllName.Substring(0, dllName.Length - 4) + ",";
            return _dllContentList.Find(contentList => (contentList.DLLName.StartsWith(match, StringComparison.OrdinalIgnoreCase)));
        }

        public List<Type> GetAllTypes()
        {
            return _typeLookup.AllItems;
        }

#if NO_SILVERLIGHT
        public List<Type> GetAllTypes(Predicate<Type> filter)
        {
            List<Type> results = new List<Type>(GetAllTypes());
            results.RemoveAll(filter);
            return results;
        }
#endif

        public List<Type> FindAvailableInterfaces(Type interfaceType)
        {
            return _cachedTypesSupportingInterface.Get(interfaceType,
                 t => _typeLookup.AllItems.FindAll(type => interfaceType != type && interfaceType.IsAssignableFrom(type)));
        }

        public T CreateInstanceOfType<T>(Type type) where T : class
        {
            ConstructorInfo constructor = GetConstructorInfo(type);
            object returnObject = constructor.Invoke(null);
            return returnObject as T;
        }

        public T CreateInstanceOfType<T>(string typeName) where T : class
        {
            Type t = GetType(typeName);
            if (t == null) return default(T);
            return CreateInstanceOfType<T>(t);
        }

        public object CreateInstanceFromType(string typeName)
        {
            Type t = GetType(typeName);
            if (t == null) return null;
            return CreateInstanceOfType<object>(t);
        }

        public object CreateInstanceFromType(Type t) { return CreateInstanceOfType<object>(t); }

        public Type GetType(string typeName)
        {
            Type t;

            if (_typeLookup.TryGetValue(typeName, out t))
                return t;

            if (_typeLookup.TryGetValue("BrainTechLLC." + typeName, out t))
                return t;

            return null;
        }

        public Type GetType(string typeName, bool throwOnTypeNotFound)
        {
            Type t = GetType(typeName);
            if (t == null && throwOnTypeNotFound) { throw new Exception("Could not find type " + typeName); }
            return t;
        }

        public ConstructorInfo GetConstructorInfo(Type t)
        {
            return _constructorLookup.Get(t, type => t.GetConstructor(System.Type.EmptyTypes));
        }

        private void InitializeTypeProvider()
        {
#if NO_SILVERLIGHT
            ReadAllTypesFromAssemblies();

            Directory.GetFiles(".", PluginDLLWildcard).ForEachMatch(
                 file => (FindDLLContents(file.Substring(2)) == null),
                 file =>
                 {
                     Assembly assembly = Assembly.LoadFrom(file);
                     DLLContents contents = GetDLLContents(assembly.FullName);
                     assembly.GetTypes().ForEach(type => AddType(contents, type));
                 });
#else
            // HEREHERE   
#endif
            PerformInitializations();
        }

#if NO_SILVERLIGHT
        private object ReadAllTypesFromAssemblies()
        {
            Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();

            foreach (Assembly asm in assemblies)
            {
                if (asm.GlobalAssemblyCache || (!string.IsNullOrEmpty(PluginPrefix) && !asm.FullName.StartsWith(PluginPrefix)))
                {
                    continue;
                }

                DLLContents dllContents = GetDLLContents(asm.FullName);
                Type[] types = null;

                try
                {
                    types = asm.GetTypes();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                    Debug.WriteLine(ex.ToString());
                }

                foreach (Type t in types)
                {
                    if (!t.IsTypeOkay())
                        continue;

                    if (_typeLookup.ContainsKey(t.FullName))
                        continue;

                    //if (typeof(IPerformsInitialization).IsAssignableFrom(t))
                    //{
                    //}

                    AddType(dllContents, t);
                }
            }
            return null;
        }
#endif
        public bool AddTypes(DLLContents dllContents, IEnumerable<Type> types)
        {
            bool allFound = true;
            foreach (var t in types)
            {
                allFound = allFound && AddType(dllContents, t);
            }
            return allFound;
        }

        public bool AddType(DLLContents dllContents, Type type)
        {
            dllContents.TypesInDLL.AddIfNew(type, () => new TypeDescription(dllContents.DLLName, type));
            bool b = _typeLookup.AddIfNew(type.FullName, () => type);
            _typeLookup.MarkDirty();
            return b;
        }

        private void PerformInitializations()
        {
            var v = from item in
                        (from type in _typeLookup.AllItems
                         where typeof(IPerformsInitialization) != type && typeof(IPerformsInitialization).IsAssignableFrom(type)
                         select CreateInstanceOfType<IPerformsInitialization>(type))
                    orderby item.RequestedOrder, item.Priority
                    select item;

            List<IPerformsInitialization> list = v.ToList();

            ResolveDependencies(list);

            // Find a load order that works with the specified dependencies
            int totalTries = ReorderForDependencies(list);

            list.ForEach(typeInstance => typeInstance.Initialize(this));
        }

        public void ResolveDependencies(List<IPerformsInitialization> list)
        {
            var missingDependencies = from examining in list
                                      where (examining.DependencyTypeNames != null)
                                      select (from dependency in examining.DependencyTypeNames
                                              where (list.Find(other => other.GetType().FullName.EndsWith(dependency, StringComparison.OrdinalIgnoreCase)) == null)
                                              select string.Format("Dependency {0} referenced by {1} not found", dependency, examining.GetType().FullName));

            var missingStrings = (from missing in
                                      (from dep in missingDependencies where dep != null && dep.Any() select dep)
                                  let missingString = missing.BuildList(dependency => dependency)
                                  select missingString).ToList();

            if (missingStrings.Count > 0)
                throw new Exception(missingStrings.BuildList(s => s));
        }

        public int ReorderForDependencies(List<IPerformsInitialization> list)
        {
            int totalTries = 0;

        TryAgain:
            if (totalTries++ == 10000)
                throw new Exception("Circular Dependencies exist");

            for (int n = 0; n < list.Count; n++)
            {
                IPerformsInitialization examining = list[n];
                if (examining.DependencyTypeNames != null)
                {
                    bool correctlyOrdered = true;

                    for (int a = 0; a < examining.DependencyTypeNames.Length; a++)
                    {
                        string dependency = examining.DependencyTypeNames[a];

                        for (int b = n + 1; b < list.Count; b++)
                        {
                            IPerformsInitialization other = list[b];
                            if (other.GetType().FullName.EndsWith(dependency, StringComparison.OrdinalIgnoreCase))
                            {
                                list.MoveDown(item => item.GetType().Equals(examining.GetType()));
                                correctlyOrdered = false;
                            }

                            if (!correctlyOrdered)
                                goto TryAgain;
                        }
                    }
                }
            }

            return totalTries;
        }

        private TypeProvider()
        {
            InitializeTypeProvider();
        }
    }

    public static class Ex3
    {
        public static bool IsTypeOkay(this Type t)
        {
            if (t == null ||
#if NO_SILVERLIGHT
 t.IsNested ||
#endif
 t.IsSpecialName || t.IsGenericTypeDefinition || typeof(EventArgs).IsAssignableFrom(t)) //|| t.IsValueType 
                return false;

            string name = t.Name;

            if (t.AssemblyQualifiedName.Contains("DisplayClass") || t.FullName.Contains("DisplayClass"))
                return false;

            if (t.AssemblyQualifiedName.Contains("AnonymousType"))
                return false;

            if (name.Contains("ActionParameters") || name.Contains("ExpandableObjectConverter") ||
                name.Contains("CharTypedCommand") || name.Contains("DisplayNameAttribute") || name.Contains("EnumExt") ||
                name.Contains("FilterCharListDelegate") || name.Contains("IDescriptorData") ||
                name.Contains("INotifyCollectionChanged") || name.Contains("INotifyPropertyChanging") ||
                name.Contains("NonSerializedAttribute") || name.Contains("OnDeserializedAttribute") ||
                name.Contains("SerializableAttribute") || name.Contains("SerializedAttribute") ||
                name.Contains("Serialization") || name.Contains("SerializationHelper") ||
                name.Contains("SLExtensions") || name.Contains("TypeAndProperties") ||
                name.Contains("TypeAndPropExt"))
                return false;

            if (name.StartsWith("Direct"))
            {
                if (name.Equals("Direct"))
                {
                    return false;
                }
            }
            return true;
        }
    }
}
